import time

from eval_lib.databases.redis.df_env import DFEnvInfo
from . import const as deploy_const
from eval_lib.common.logger import get_logger

log = get_logger()

GLOBAL_LOCK = "get_env_info"

ENV_STATUS_FREE = "0"
ENV_STATUS_MONOPOLIZE = "-1"

ENV_RESERVED_FREE = "0"
ENV_RESERVED_CONCURRENCY = "1"
ENV_RESERVED_MONOPOLIZE = "-1"

ENV_DEPLOY_STATUS_INIT = "init"
ENV_DEPLOY_STATUS_COMPLETE = "complete"

GET_DF_ENV_TIMEOUT = 20 * 60

class DFEnvs(object):

    def __init__(self, uuid=None):
        self.uuid = uuid
        self.df_envs = []
        self.redis = DFEnvInfo()

    def update(self, name, **kwargs):
        lock = self.redis.acquire_lock(GLOBAL_LOCK)
        for env in self.df_envs:
            if env["name"] == name:
                env.update(kwargs)
                self.redis.update(name, kwargs)
        self.redis.release_lock(GLOBAL_LOCK, lock)

    def init(self, name, type):
        df_info = {
            "name": name,
            "deploy_status": ENV_DEPLOY_STATUS_INIT,
            "status": ENV_STATUS_FREE,
            "concurrency": "0",
            "mgt_ip": "",
            "server_query_port": "",
            "server_controller_port": "",
            "updated_time": str(int(time.time())),
            "reserved": "0",  # Reserve environments to prevent concurrent use cases from being blocked when all environments are occupied
            "type": type,
        }
        if type in [
            deploy_const.ENV_TYPE_DEEPFLOW_CE,
            deploy_const.ENV_TYPE_DEEPFLOW_EE,
            deploy_const.ENV_NAME_MAP[deploy_const.ENV_TYPE_DEEPFLOW_CE],
            deploy_const.ENV_NAME_MAP[deploy_const.ENV_TYPE_DEEPFLOW_EE]
        ]:
            self.df_envs.append(df_info)
        lock = self.redis.acquire_lock(GLOBAL_LOCK)
        self.redis.init_envs(self.df_envs, self.uuid)
        self.redis.release_lock(GLOBAL_LOCK, lock)

    def update_reserved(self) -> int:
        '''
        return int 返回可用df环境的数量
        '''
        envs = []
        df_envs = self.get_all_envs()
        for _, df_env in df_envs.items():
            if df_env["deploy_status"] == ENV_DEPLOY_STATUS_COMPLETE:
                envs.append(df_env)
        if len(envs) > 2:
            envs[0]["reserved"] = ENV_RESERVED_FREE
            envs[1]["reserved"] = ENV_RESERVED_CONCURRENCY
            envs[2]["reserved"] = ENV_RESERVED_MONOPOLIZE
        if len(envs) > 3:
            for env in envs[3:]:
                env["reserved"] = ENV_RESERVED_MONOPOLIZE
        for env in envs:
            lock = self.redis.acquire_lock(GLOBAL_LOCK)
            self.redis.update_env_reserved(env["name"], env["reserved"])
            self.redis.release_lock(GLOBAL_LOCK, lock)
        return len(envs)
    
    def get_df_envs(self):
        # Simple LB Sort by concurrency Lowest to highest
        if not self.uuid:
            return
        lock = self.redis.acquire_lock(GLOBAL_LOCK)
        envs = self.redis.get_prefix_envs(self.uuid)
        self.redis.release_lock(GLOBAL_LOCK, lock)
        envs = {
            name: env for name, env in envs.items() if env["type"] in [
                deploy_const.ENV_NAME_MAP[deploy_const.ENV_TYPE_DEEPFLOW_CE],
                deploy_const.ENV_NAME_MAP[deploy_const.ENV_TYPE_DEEPFLOW_EE]
            ]
        }
        keys = sorted(envs, key=lambda x: get_concurrency(envs, x))
        df_envs = {}
        for key in keys:
            df_envs[key] = envs[key]
        return df_envs

    def get_all_envs(self):
        if not self.uuid:
            return
        lock = self.redis.acquire_lock(GLOBAL_LOCK)
        envs = self.redis.get_prefix_envs(self.uuid)
        self.redis.release_lock(GLOBAL_LOCK, lock)
        return envs

    def update_env_status(self, env_name, status):
        lock = self.redis.acquire_lock(GLOBAL_LOCK)
        self.redis.update_env_status(env_name, status)
        self.redis.release_lock(GLOBAL_LOCK, lock)

    def update_env_concurrency(self, env_name, concurrency):
        lock = self.redis.acquire_lock(GLOBAL_LOCK)
        self.redis.update_env_concurrency(env_name, concurrency)
        self.redis.release_lock(GLOBAL_LOCK, lock)

    def list_env_uuids(self):
        lock = self.redis.acquire_lock(GLOBAL_LOCK)
        envs = self.redis.get_envs_key_info()
        self.redis.release_lock(GLOBAL_LOCK, lock)
        return envs

    def delete_envs_by_uuid(self):
        lock = self.redis.acquire_lock(GLOBAL_LOCK)
        self.redis.delete_env(self.uuid)
        self.redis.release_lock(GLOBAL_LOCK, lock)

    def delete_by_instance_name(self, instance_name):
        lock = self.redis.acquire_lock(GLOBAL_LOCK)
        self.redis.delete_by_instance_name(self.uuid, instance_name)
        envs = self.redis.get_prefix_envs(self.uuid)
        if not envs:
            self.redis.delete_env(self.uuid)
        self.redis.release_lock(GLOBAL_LOCK, lock)

    def get_monopolize_df_env(self):
        start_time  = int(time.time())
        while True:
            time_consumed = int(time.time()) - start_time
            identifier = self.redis.acquire_lock(self.uuid)
            envs = self.get_df_envs()
            if not envs:
                log.error(f"No normal deepflow found !")
                return None
            available_env = False
            for name, env in envs.items():
                if env["deploy_status"] != ENV_DEPLOY_STATUS_COMPLETE:
                    continue
                available_env = True
                if env["reserved"] == ENV_RESERVED_CONCURRENCY:
                    continue
                # The environment is already occupied by concurrent use cases
                if env["status"] != ENV_STATUS_FREE or env["concurrency"
                                                            ] != "0":
                    continue
                if env["status"] == ENV_STATUS_MONOPOLIZE:
                    continue
                else:
                    status = ENV_STATUS_MONOPOLIZE
                    self.update_env_status(name, status)
                    return env
            # release  lock
            self.redis.release_lock(self.uuid, identifier)
            if not available_env:
                log.error(f"wait env error, No normal deepflow found !")
                return None
            if time_consumed  >= GET_DF_ENV_TIMEOUT / 2:
                log.info(
                    f"wait env {time_consumed}s! deepflow maybe deploy failed"
                )
            if time_consumed  >= GET_DF_ENV_TIMEOUT:
                log.error(f"Time out!  No normal deepflow found !")
                return None
            time.sleep(10)

    def get_concurrency_df_env(self):
        start_time  = int(time.time())
        while True:
            time_consumed = int(time.time()) - start_time
            identifier = self.redis.acquire_lock(self.uuid)
            envs = self.get_df_envs()
            if not envs:
                log.error(f"No normal deepflow found !")
                return None
            available_env = False
            for name, env in envs.items():
                if env["deploy_status"] != ENV_DEPLOY_STATUS_COMPLETE:
                    continue
                available_env = True
                if env["reserved"] == ENV_RESERVED_MONOPOLIZE:
                    continue
                # The env has been monopolized
                if env["status"] == ENV_STATUS_MONOPOLIZE:
                    continue
                else:
                    # concurrency+1
                    concurrency = str(int(env["concurrency"]) + 1)
                    self.update_env_concurrency(name, concurrency)
                    return env
            # release  lock
            self.redis.release_lock(self.uuid, identifier)
            if not available_env:
                log.error(f"wait env error, No normal deepflow found !")
                return None
            if time_consumed  >= GET_DF_ENV_TIMEOUT:
                log.error(f"Time out!  No normal deepflow found !")
                return None
            if time_consumed  >= GET_DF_ENV_TIMEOUT / 2:
                log.info(
                    f"wait env {time_consumed}s! deepflow maybe deploy failed"
                )
            time.sleep(10)

    def release_df_env(self, mgt_ip):
        identifier = self.redis.acquire_lock(self.uuid)
        envs = self.get_df_envs()
        for name, env in envs.items():
            if env["mgt_ip"] != mgt_ip:
                continue
            env_concurrency = int(env["concurrency"])
            if env["status"] == ENV_STATUS_MONOPOLIZE:
                status = ENV_STATUS_FREE
                self.update_env_status(name, status)
            elif env_concurrency > 0:
                concurrency = str(int(env["concurrency"]) - 1)
                status = ENV_STATUS_FREE
                self.update_env_concurrency(name, concurrency)
            else:
                log.error("env release error, check it")
            break
        self.redis.release_lock(self.uuid, identifier)

def get_concurrency(envs, name):
    if envs[name]['status'] == ENV_STATUS_MONOPOLIZE:
        return 9999
    else:
        return int(envs[name]['concurrency'])
